/*------------------------------------------------------------------------------
 *
 * Code to test the paper:
 * Oncina, J.
 * "Optimum Algorithm to Minimize Human Interactions in Sequential Computer
 * Assisted Pattern Recognition"
 * Pattern Recognition Letters, vol. 30, pp. 558-563 (2009)
 *
 *----------------------------------------------------------------------------*/

#include <iostream>
#include <stdlib.h>
#include <iomanip>   // setw(4)
#include <vector>
#include <math.h>

#include "CFiniteStateTransducer.hpp"
#include "asrtbas.h"
using namespace std;


//------------------------------------------------------------------------------
//
CFiniteStateTransducer* generateRealWorldFromFile( const char* filename )
{
    cout << "> Load from file: " << filename << endl;

    CFiniteStateTransducer *dbRealWorld = new CFiniteStateTransducer( filename );

    dbRealWorld->normalizeUsingJointProbability();
    dbRealWorld->print();

    return dbRealWorld;
}

//------------------------------------------------------------------------------
//
CFiniteStateTransducer* generateRealWorldWithRandomData( unsigned int numInputSymbols,
                        unsigned int numOutputSymbols, unsigned int num_states )
{
    cout << "> Random generation " << endl;

    CFiniteStateTransducer *dbRealWorld = new CFiniteStateTransducer( numInputSymbols,
                                                numOutputSymbols, num_states );

    dbRealWorld->generateRandomProbability();

    dbRealWorld->normalizeUsingJointProbability();
    dbRealWorld->print();

    return dbRealWorld;
}

//------------------------------------------------------------------------------
//
void generateDataFromRealWorld( const CFiniteStateTransducer *dbRealWorld,
                    CFiniteStateTransducer **pX_Y,
                    CFiniteStateTransducer **pX,
                    CFiniteStateTransducer **pY,
                    CFiniteStateTransducer **pY_X )
{
    unsigned int numInputSymbols = dbRealWorld->numInputSymbols();
    unsigned int numOutputSymbols = dbRealWorld->numOutputSymbols();
    unsigned int num_states = dbRealWorld->numStates();

    cout << "\nNum Input Symbols : " << numInputSymbols << endl;
    cout << "Num Output Symbols: " << numOutputSymbols << endl;
    cout << "Num States        : " << num_states << endl;


    cout << "\n2 - GENERATE p(X) -----------------------------------------------" << endl;
    //     Una matriz por cada símbolo. Para calcularlas sumamos las matrices
    //     del mundo real que tienen ese símbolo.

    *pX = new CFiniteStateTransducer( numInputSymbols, 0, num_states );
    (*pX)->generateFromInputSymbols( dbRealWorld );
    (*pX)->print();


    cout << "\n3 - GENERATE p(Y) -----------------------------------------------" << endl;

    *pY = new CFiniteStateTransducer( numOutputSymbols, 0, num_states );
    (*pY)->generateFromOutputSymbols( dbRealWorld );
    (*pY)->print();


    cout << "\n4 - GENERATE p(X|Y) ---------------------------------------------" << endl;
    //     Probabilidad de X dado Y.
    //     La probabilidad de los arcos que salen de un estado con el mismo
    //     símbolo de salida (Y) suma 1.

    *pX_Y = new CFiniteStateTransducer( dbRealWorld );
    (*pX_Y)->normalizeUsingOutputValues();
    (*pX_Y)->print();


    cout << "\n5 - GENERATE p(Y|X) ---------------------------------------------";

    *pY_X = new CFiniteStateTransducer( dbRealWorld );
    (*pY_X)->normalizeUsingInputValues();
    (*pY_X)->print();
}

//-----------------------------------------------------------------------------
//
double prv_getBayesProbability( const CFiniteStateTransducer *pX_Y,
                            const CFiniteStateTransducer *pX,
                            const CFiniteStateTransducer *pY,
                            const vector<unsigned int> &inputSymbolsList,
                            const vector<unsigned int> &outputSymbolsList )
{
    vector<unsigned int> emptyList;

    double prob_pX_Y = pX_Y->getProbability( inputSymbolsList, outputSymbolsList );

    double prob_pX = pX->getProbability( inputSymbolsList, emptyList );

    double prob_pY = pY->getProbability( outputSymbolsList, emptyList );

    cout << "prob_pX_Y = " <<  prob_pX_Y  << endl;
    cout << "prob_pX = " <<  prob_pX  << endl;
    cout << "prob_pY = " <<  prob_pY  << endl;

    // pY_X = ( pY * pX_Y ) / pX
    return ( (prob_pY * prob_pX_Y) / prob_pX );
}

//-----------------------------------------------------------------------------
//
void prv_testString( const CFiniteStateTransducer *pX_Y,
                     const CFiniteStateTransducer *pX,
                     const CFiniteStateTransducer *pY,
                     const CFiniteStateTransducer *pY_X,
                     const vector<unsigned int> &inputSymbolsList,
                     const vector<unsigned int> &outputSymbolsList )
{
    double epsilon = 0.00001;

    double prob_Bayes = prv_getBayesProbability( pX_Y, pX, pY,
                                             inputSymbolsList, outputSymbolsList );

    double prob_pY_X = pY_X->getProbability( outputSymbolsList, inputSymbolsList );
    cout << "prob_pY_X = " <<  prob_pY_X  << endl;


    cout << " => ¿ " << prob_Bayes;
    cout << " == " << prob_pY_X;
    cout << " ? " << endl;

    assert( fabs ( prob_Bayes - prob_pY_X ) < epsilon );
}

//-----------------------------------------------------------------------------
//
void testGeneratedDataUsingBayes( const CFiniteStateTransducer *pX_Y,
                            const CFiniteStateTransducer *pX,
                            const CFiniteStateTransducer *pY,
                            const CFiniteStateTransducer *pY_X )
{

    cout << "\n6 - TESTING GENERATED DATA --------------------------------------" << endl;


    vector<unsigned int> inputSymbolsList;
    vector<unsigned int> outputSymbolsList;


    cout << "\nTEST 1 - Empty String" << endl;

    prv_testString( pX_Y, pX, pY, pY_X, inputSymbolsList, outputSymbolsList );



    cout << "\nTEST 2 - String with symbols" << endl;

    inputSymbolsList.push_back( 0 );
    outputSymbolsList.push_back( 0 );

    prv_testString( pX_Y, pX, pY, pY_X, inputSymbolsList, outputSymbolsList );

}

//-----------------------------------------------------------------------------
//
int main(int argc, char** argv)
{
    CFiniteStateTransducer *dbRealWorld, *pX_Y, *pX, *pY, *pY_X;


    cout << "\n1 - GENERATE THE REAL WORLD p(X,Y) ------------------------------" << endl;
    //     Transductor de estados finitos generado aleatoriamente.
    //     La suma de los arcos que salen de un estado más la probabilidad de
    //     de terminar en ese estado suma 1.

    #if 1

        dbRealWorld = generateRealWorldFromFile( "inputdata.fst" );

    #else

        unsigned int numInputSymbols = 2;
        unsigned int numOutputSymbols = 1;
        unsigned int numStates = 2;
        dbRealWorld = generateRealWorldWithRandomData( numInputSymbols,
                                                   numOutputSymbols, numStates );
    #endif


    generateDataFromRealWorld( dbRealWorld, &pX_Y, &pX, &pY, &pY_X );


    testGeneratedDataUsingBayes( pX_Y, pX, pY, pY_X );


    delete dbRealWorld;
    delete pX_Y;
    delete pX;
    delete pY;
    delete pY_X;


    return (EXIT_SUCCESS);
}

/**
*/
